<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/3/27
 * Time: 10:12
 */

namespace EasyTiktokSdk\Kernel;

use EasyTiktokSdk\Application;
use EasyTiktokSdk\Kernel\Http\Response;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

use EasyTiktokSdk\Kernel\Contracts\AccessTokenInterface;
use EasyTiktokSdk\Kernel\Traits\HasHttpRequests;


class HttpClient
{
    use HasHttpRequests { request as performRequest; }

    /**
     * @var \EasyTiktokSdk\Application;
     */
    protected $app;

    /**
     * @var AccessTokenInterface;
     */
    protected $accessToken;

    /**
     * @var
     */
    protected $baseUri = 'https://open-api.tiktokglobalshop.com';

    protected $sign = true;

    /**
     * HttpClient constructor.
     * @param Application $app
     * @param AccessTokenInterface $accessToken
     */
    public function __construct(Application $app, AccessTokenInterface $accessToken = null)
    {
        $this->app = $app;
        $this->accessToken = $accessToken ? $accessToken : $this->app['access_token'];

        if (method_exists($this, '_init')) {
            $this->_init();
        }
    }

    /**
     * 获取请求sign字符串：签名算法
     *
     * @param string    $path   接口路径
     * @param array     $params 请求参数数组
     * @return false|string 签名算法得到的结果字符串
     */
    protected function getSign($path = '', array $params = [], $body = '')
    {
        // 店铺app秘钥
        $secret = $this->app['config']['app_secret'];
        unset($params['access_token']);

        //1,排序数据
        ksort($params);   
        $sign_str = '';
        // 2 拼接query字符串
        foreach ($params as $key => $val) {
            $sign_str .= $key . $val;
        }
        //3,拼接path
        $sign_str = $path . $sign_str;
        // json 请求增加 body
        if (!empty($body)) {
            $sign_str = $sign_str . $body;
        }
        //4，前后加 app秘钥
        $sign_str = $secret . $sign_str . $secret;
        //5，使用16进制进行编码
        $sign_str = hash_hmac('sha256', $sign_str, $secret);
        return $sign_str;
    }

    protected function getCommonParams()
    {
        return [
            'timestamp' => time(),
            'app_key'   => $this->app['config']['app_key'],
            'shop_cipher' => $this->app['config']['shop_cipher'],
        ];
    }

    /**
     * GET request.
     *
     * @param string $url
     * @param array  $query
     *
     * @return \Psr\Http\Message\ResponseInterface|array|object|string
     *
     * @throws \Exception
     */
    public function httpGet($url, array $query = [])
    {
        return $this->request($url, 'GET', ['query' => $query]);
    }

    /**
     * POST request.
     *
     * @param string $url
     * @param array  $data
     * @param array  $query
     * @return \Psr\Http\Message\ResponseInterface|array|object|string
     *
     * @throws \Exception
     */
    public function httpPost($url, array $data = [], array $query = [])
    {
        return $this->request($url, 'POST', ['query' => $query, 'form_params' => $data]);
    }

    /**
     * JSON request.
     *
     * @param string       $url
     * @param string|array $data
     * @param array        $query
     * @param string       $base_uri
     *
     * @return \Psr\Http\Message\ResponseInterface|array|object|string
     *
     * @throws \Exception
     */
    public function httpPostJson($url, array $data = [], array $query = [], $base_uri = '')
    {
        return $this->request($url, 'POST', ['query' => $query, 'json' => $data, 'base_uri' => $base_uri]);
    }

    /**
     * @param string $url
     * @param array  $data
     * @param array  $query
     * @param $base_uri
     * @return array|object|ResponseInterface|string
     * @throws \Exception
     */
    public function httpPutJson($url, array $data = [], array $query = [], $base_uri = '')
    {
        return $this->request($url, 'PUT', ['query' => $query, 'json' => $data, 'base_uri' => $base_uri]);
    }


    /**
     * @param string $url
     * @param array $data
     * @param array $query
     * @param $base_uri
     * @return array|object|ResponseInterface|string
     * @throws \Exception
     */
    public function httpDelJson($url, array $data = [], array $query = [], $base_uri = '')
    {
        return $this->request($url, 'DELETE', ['query' => $query, 'json' => $data, 'base_uri' => $base_uri]);
    }

    /**
     * @return AccessTokenInterface
     */
    public function getAuthToken()
    {
        return $this->accessToken;
    }

    /**
     * @param AccessTokenInterface $authToken
     */
    public function setAuthToken(AccessTokenInterface $authToken)
    {
        $this->accessToken = $authToken;
    }


    /**
     * @param string $url
     * @param string $method
     * @param array  $options
     * @param bool   $returnRaw
     *
     * @return \Psr\Http\Message\ResponseInterface|array|object|string
     *
     * @throws \Exception
     */
    protected function request($url, $method = 'GET', array $options = [], $returnRaw = false)
    {
        if (empty($this->middlewares)) {
            $this->registerHttpMiddlewares();
        }

        $query = $this->getCommonParams();

        if (!empty($options['except'])) {
            if (is_string($options['except'])) {
                unset($query[$options['except']]);
            } elseif (is_array($options['except'])) {
                foreach ($options['except'] as $exceptItem) {
                    unset($query[$exceptItem]);
                }
            }
        }

        if (isset($options['query']) && is_array($options['query'])) {
            $query = array_merge($query ? $query : [], $options['query']);
        }
        $json1 = '';
        
        if (isset($options['json']) && is_array($options['json'])) {
            $options = $this->fixJsonIssue($options);
            $json1 = $options['body'];
        }
        
        if ($this->sign) {
            $query['sign'] = $this->getSign($url, $query, $json1);
        }
        $this->accessToken->applyToRequest($query);

        $options['query'] = $query;

        $options['headers'] = array_merge($options['headers'] ? $options['headers'] : [],
            ['x-tts-access-token' => $this->accessToken->getQueryName()]
        );

        $response = $this->performRequest($url, $method, $options);

        if ($returnRaw) {
            return $response;
        }

        // 返回数组
        /**
         * @var \EasyTiktokSdk\Kernel\Http\Response $response
         */
        $response = Response::buildFromPsrResponse($response);
        $response->getBody()->rewind();
        return $response->toArray();
    }


    /**
     * Register Guzzle middlewares.
     */
    protected function registerHttpMiddlewares()
    {
        // retry
        $this->pushMiddleware($this->retryMiddleware(), 'retry');
    }


    /**
     * Return retry middleware.
     *
     * @return \Closure
     */
    protected function retryMiddleware()
    {
        return Middleware::retry(function (
            $retries,
            RequestInterface $request,
            ResponseInterface $response = null
        ) {
            // Limit the number of retries to 2
            if ($retries < 1 && $response && $body = $response->getBody()) {
                // Retry on server errors
                $response = json_decode($body, true);

                if (!empty($response['code']) && in_array(abs($response['code']), [11, 29], true)) {
                    $this->accessToken->refresh();

                    return true;
                }
            }

            return false;
        }, function () {
            return 500;
        });
    }

}